import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import { createPortal } from 'react-dom';
import {
  Announcements,
  DndContext,
  closestCenter,
  KeyboardSensor,
  PointerSensor,
  useSensor,
  useSensors,
  DragStartEvent,
  DragOverlay,
  DragMoveEvent,
  DragEndEvent,
  DragOverEvent,
  MeasuringStrategy,
  DropAnimation,
  Modifier,
  defaultDropAnimation,
  UniqueIdentifier,
  CollisionDetection,
  pointerWithin,
  rectIntersection,
  getFirstCollision,
} from '@dnd-kit/core';
import {
  SortableContext,
  arrayMove,
  verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';

import {
  buildTree,
  flattenTree,
  getProjection,
  getChildCount,
  removeItem,
  removeChildrenOf,
  setProperty,
  findItemDeep
} from './utilities';
import { sortableTreeKeyboardCoordinates } from './keyboardCoordinates';
import SortableTreeItem from './components/SortableTreeItem';
import { useDispatch, useSelector } from 'react-redux';
import { State } from '@/reducers';
import actionTypes from '@/actions/action-types'
import {
  editorSelectedCommand,
  editorSelectedCommandIndex,
  getBreakpoints,
  getCurrentMacroId,
  getDoneCommandIndices,
  getErrorCommandIndices,
  getWarningCommandIndices
} from '@/recomputed'
import { addBreakpoint, removeBreakpoint } from '@/actions'
import DroppableContainer from './components/DroppableContainer'
import DraggableItem from './components/DraggableItem'
import {
  availableCommands
} from '@/common/command'
import RightSidebar from '@/components/RightSidebar'
import type { FlattenedItem, SensorContext, TreeItems } from './types';

const measuring = {
  droppable: {
    strategy: MeasuringStrategy.Always,
  },
};

const dropAnimationConfig: DropAnimation = {
  keyframes({ transform }) {
    return [
      { opacity: 1, transform: CSS.Transform.toString(transform.initial) },
      {
        opacity: 0,
        transform: CSS.Transform.toString({
          ...transform.final,
          x: transform.final.x + 5,
          y: transform.final.y + 5,
        }),
      },
    ];
  },
  easing: 'ease-out',
  sideEffects({ active }) {
    active.node.animate([{ opacity: 0 }, { opacity: 1 }], {
      duration: defaultDropAnimation.duration,
      easing: defaultDropAnimation.easing,
    });
  },
};

interface Props {

  defaultItems?: TreeItems;
  indentationWidth?: number;
  indicator?: boolean;
  removable?: boolean;
}

const containers: UniqueIdentifier[] = ['commands', 'allFlattenedItems'];

const adjustTranslate: Modifier = ({ transform }) => {
  return {
    ...transform,
    y: transform.y - 25,
  };
};

export default function SortableTree({
  indicator = false,
  indentationWidth = 30,
  removable,
}: Props) {
  const disPatch = useDispatch()
  const draggingItemRef = useRef<{ id: UniqueIdentifier, removed: boolean } | null>(null)
  const items = useSelector((state: State) => state.editor.editing.commands); // 使用拉平的数据
  const nextCommandIndex = useSelector((state: State) => state.player.nextCommandIndex)
  const state = useSelector((state: State) => state)
  const breakpointIds = getBreakpoints(state)
  const editing = useSelector((state: State) => state.editor.editing)
  const doneCommandIds = getDoneCommandIndices(state)
  const errorCommandIds = getErrorCommandIndices(state)
  const warningCommandIds = getWarningCommandIndices(state)
  const selectedCommand = editorSelectedCommand(state)
  const selectedCommandId = editorSelectedCommandIndex(state)

  const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
  const [overId, setOverId] = useState<UniqueIdentifier | null>(null);
  const [offsetLeft, setOffsetLeft] = useState(0);
  const [commands, setCommands] = useState(availableCommands.map((item: any) => ({
    id: window.crypto.randomUUID(),
    cmd: item,
    target: '',
    value: ''
  })))
  const [currentPosition, setCurrentPosition] = useState<{
    parentId: UniqueIdentifier | null;
    overId: UniqueIdentifier;
  } | null>(null);


  const setItems = (getItems: any) => {
    const commands = typeof getItems === 'function' ? getItems(items) : getItems
    disPatch({
      type: actionTypes.UPDATE_COMMANDS,
      data: commands
    })
  }
  // 展开平铺tree后，所有item
  const allFlattenedItems = useMemo(() => {
    return flattenTree(items);
  }, [items])

  // 页面展示的items
  const flattenedItems = useMemo(() => {
    // 折叠的item的id list
    const collapsedItems = allFlattenedItems.reduce(
      (acc: any, { collapsed, id }: any) =>
        collapsed ? [...acc, id] : [...acc],
      []
    );

    // 移除折叠的和当前正在拖动的
    return removeChildrenOf(
      allFlattenedItems,
      activeId !== null ? [activeId, ...collapsedItems] : collapsedItems
    );
  }, [activeId, allFlattenedItems]);

  // 找到当前的容器
  const findContainer = (id: UniqueIdentifier) => {
    if (containers.includes(id)) {
      return id;
    }
    if (commands.find((item) => item.id === id)) {
      return 'commands';
    } else if (allFlattenedItems.find((item) => item.id === id)) {
      return 'allFlattenedItems';
    }
  }

  const projected = activeId && overId ? getProjection(
    flattenedItems,
    activeId,
    overId,
    offsetLeft,
    indentationWidth
  ) : null;

  const sensorContext: SensorContext = useRef({
    items: flattenedItems,
    offset: offsetLeft,
  })

  const [coordinateGetter] = useState(() =>
    sortableTreeKeyboardCoordinates(sensorContext, indicator, indentationWidth)
  )

  const [clonedItems, setClonedItems] = useState<any>()

  const sensors = useSensors(
    useSensor(PointerSensor),
    useSensor(KeyboardSensor, {
      coordinateGetter,
    })
  )

  const sortedIds = useMemo(
    () => flattenedItems.map(({ id }) => id),
    [flattenedItems]
  )

  const activeItem = activeId ? flattenedItems.find(({ id }) => id === activeId) || commands.find(({ id }) => id === activeId) : null;

  const collisionDetectionStrategy: CollisionDetection = useCallback(
    (args) => {

      // Start by finding any intersecting droppable
      const pointerIntersections = pointerWithin(args);
      const intersections =
        pointerIntersections.length > 0
          ? // If there are droppables intersecting with the pointer, return those
          pointerIntersections
          : rectIntersection(args);
      let overId = getFirstCollision(intersections, 'id') ?? null;

      if (overId !== null) {

        if (containers.includes(overId)) {
          const containerItems = allFlattenedItems;

          // If a container is matched and it contains items (columns 'A', 'B', 'C')
          if (containerItems.length > 0) {
            // Return the closest droppable within that container
            overId = closestCenter({
              ...args,
              droppableContainers: args.droppableContainers.filter(
                (container) =>
                  container.id !== overId &&
                  containerItems.find((item) => item.id === container.id)
              ),
            })[0]?.id;
          }
        }

        return [{ id: overId }];
      }

      return [];
    },
    [activeId, allFlattenedItems]
  )

  const announcements: Announcements = {
    onDragStart({ active }) {
      return `Picked up ${active.id}.`;
    },
    onDragMove({ active, over }) {
      return getMovementAnnouncement('onDragMove', active.id, over?.id);
    },
    onDragOver({ active, over }) {
      return getMovementAnnouncement('onDragOver', active.id, over?.id);
    },
    onDragEnd({ active, over }) {
      return getMovementAnnouncement('onDragEnd', active.id, over?.id);
    },
    onDragCancel({ active }) {
      return `Moving was cancelled. ${active.id} was dropped in its original position.`;
    },
  }

  const handleDragStart = ({ active: { id: activeId } }: DragStartEvent) => {

    setActiveId(activeId);
    setOverId(activeId); // 开始拖动，over的元素为自身
    setClonedItems(items);
    const activeItem = flattenedItems.find(({ id }) => id === activeId);

    if (activeItem) {
      setCurrentPosition({
        parentId: activeItem.parentId,
        overId: activeId,
      });
    }

    document.body.style.setProperty('cursor', 'grabbing');
  }

  const handleDragMove = ({ delta }: DragMoveEvent) => {
    setOffsetLeft(delta.x);
  }

  const handleDragOver = ({ active, over }: DragOverEvent) => {

    const overId = over?.id ?? null;
    setOverId(overId);

    if (overId === null || containers.includes(active.id)) {
      const item = overId === null && draggingItemRef.current?.id && !commands.find(({ id }: any) => id === active.id) && findItemDeep(items, active.id)
      if (item) {
        // 如果从命令列表拖拽元素到容器后又拖到容器外，则删除
        setActiveId(draggingItemRef.current?.id!)
        setOverId(draggingItemRef.current?.id!)
        draggingItemRef.current!.removed = true
        handleRemove(active.id)
      }
      return
    }

    if (containers.includes(overId) && items.length > 0) {
      return
    }

    // 如果从命令列表拖拽元素到容器内后又拖到容器外，再重新拖到容器内，则更新active.id
    if (!commands.find(({ id }: any) => id === active.id) && draggingItemRef.current?.removed) {
      active.id = draggingItemRef.current.id
      draggingItemRef.current = null
    }

    const activeContainer = findContainer(active.id);

    const overContainer = findContainer(overId);

    if (!overContainer || !activeContainer) {
      return
    }

    if (activeContainer === 'commands' && overContainer === 'allFlattenedItems') {

      // 所有的item
      const clonedItems: FlattenedItem[] = JSON.parse(
        JSON.stringify(allFlattenedItems)
      );

      // 拖动的item
      const activeIndex = commands.findIndex(({ id }: any) => id === active.id);

      // 拖动到的item
      const overIndex = clonedItems.findIndex(({ id }: any) => id === overId);

      let newIndex: number;

      if (containers.includes(overId)) {
        newIndex = clonedItems.length + 1;
      } else {
        const isBelowOverItem =
          over &&
          active.rect.current.translated &&
          active.rect.current.translated.top >
          over.rect.top + over.rect.height;

        const modifier = isBelowOverItem ? 1 : 0;

        newIndex = overIndex >= 0 ? overIndex + modifier : clonedItems.length + 1;
      }

      if (clonedItems.find(({ id }: any) => id === active.id)) {
        // 已存在相同的id了
        return
      }

      const newId = window.crypto.randomUUID()

      // 插入到新的位置
      const sortedItems = [
        ...clonedItems.slice(0, newIndex),
        { ...commands[activeIndex], depth: 0, parentId: null },
        ...clonedItems.slice(
          newIndex,
          clonedItems.length
        ),
      ]


      draggingItemRef.current = { id: newId, removed: false }


      // 把展开的数据根据parentId重新构建为tree
      const newItems = buildTree(sortedItems as any);
      const newCommands = commands.map((item) => item.id !== active.id ? item : {
        ...item,
        id: newId
      });

      setItems(newItems)
      setCommands(newCommands)

    }

  }

  const handleDragEnd = ({ active, over }: DragEndEvent) => {
    draggingItemRef.current = null
    const overId = over?.id ?? null;
    if (overId === null) {
      resetState();
      return
    }
    const activeContainer = findContainer(active.id);
    const overContainer = findContainer(overId);

    if (activeContainer === 'allFlattenedItems' && overContainer === 'allFlattenedItems' && projected && over) {
      // 由depth, parentId, over可确定移动的层级及位置
      const { depth, parentId } = projected; // 记录希望移动后的深度及parentId
      const clonedItems: FlattenedItem[] = JSON.parse(
        JSON.stringify(allFlattenedItems)
      ); // 所有的item
      const overIndex = clonedItems.findIndex(({ id }) => id === over.id); // 拖动到的index
      const activeIndex = clonedItems.findIndex(({ id }) => id === active.id); // 拖动的index
      const activeTreeItem = clonedItems[activeIndex]; // 拖动的item

      clonedItems[activeIndex] = { ...activeTreeItem, depth, parentId }; // 拖动后的深度和parentId

      const sortedItems = arrayMove(clonedItems, activeIndex, overIndex); // 排序移动
      const newItems = buildTree(sortedItems); // 把展开的数据根据parentId重新构建为tree

      setItems(newItems); // 更新tree数据
    }
    resetState();
  }

  const handleDragCancel = () => {
    resetState();
    setItems(clonedItems)
  }

  const resetState = () => {
    setOverId(null);
    setActiveId(null);
    setOffsetLeft(0);
    setCurrentPosition(null);

    document.body.style.setProperty('cursor', '');
  }

  const handleRemove = (id: UniqueIdentifier) => {

    setItems((items: any) => removeItem(items, id));
  }

  const handleCollapse = (id: UniqueIdentifier) => {

    const newItems = setProperty(items, id, 'collapsed', (value) => {
      return !value;
    })
    setItems(newItems)
  }

  // 断点
  const handleAddBreakPoint = (id: string) => {
    const macroId = getCurrentMacroId(state)
    if (breakpointIds.includes(id)) {
      disPatch(removeBreakpoint(macroId, id) as any)
    } else {
      disPatch(addBreakpoint(macroId, id) as any)
    }
  }

  // 拖动时打印日志
  const getMovementAnnouncement = (eventName: string, activeId: UniqueIdentifier, overId?: UniqueIdentifier) => {
    if (overId && projected) {
      if (eventName !== 'onDragEnd') {
        if (
          currentPosition &&
          projected.parentId === currentPosition.parentId &&
          overId === currentPosition.overId
        ) {
          return;
        } else {
          setCurrentPosition({
            parentId: projected.parentId,
            overId,
          });
        }
      }

      const clonedItems: FlattenedItem[] = JSON.parse(
        JSON.stringify(allFlattenedItems)
      );
      const overIndex = clonedItems.findIndex(({ id }) => id === overId);
      const activeIndex = clonedItems.findIndex(({ id }) => id === activeId);
      const sortedItems = arrayMove(clonedItems, activeIndex, overIndex);

      const previousItem = sortedItems[overIndex - 1];

      let announcement;
      const movedVerb = eventName === 'onDragEnd' ? 'dropped' : 'moved';
      const nestedVerb = eventName === 'onDragEnd' ? 'dropped' : 'nested';

      if (!previousItem) {
        const nextItem = sortedItems[overIndex + 1];
        if (nextItem) {
          announcement = `${activeId} was ${movedVerb} before ${nextItem.id}.`;
        }
      } else {
        if (projected.depth > previousItem.depth) {
          announcement = `${activeId} was ${nestedVerb} under ${previousItem.id}.`;
        } else {
          let previousSibling: FlattenedItem | undefined = previousItem;
          while (previousSibling && projected.depth < previousSibling.depth) {
            const parentId: UniqueIdentifier | null = previousSibling.parentId;
            previousSibling = sortedItems.find(({ id }) => id === parentId);
          }

          if (previousSibling) {
            announcement = `${activeId} was ${movedVerb} after ${previousSibling.id}.`;
          }
        }
      }

      return announcement;
    }

    return;
  }

  useEffect(() => {
    sensorContext.current = {
      items: flattenedItems,
      offset: offsetLeft,
    };
  }, [flattenedItems, offsetLeft]);


  return (
    <DndContext
      accessibility={{ announcements }} // 可访问性
      sensors={sensors} // 传感器
      measuring={measuring} // 测量用的
      collisionDetection={collisionDetectionStrategy} // 碰撞检测
      onDragStart={handleDragStart}
      onDragMove={handleDragMove}
      onDragOver={handleDragOver}
      onDragEnd={handleDragEnd}
      onDragCancel={handleDragCancel}
      cancelDrop={(args) => {
        // 如果拖拽元素在容器外，则取消
        return !args.over;
      }}
    >
      <div style={{
        display: 'flex',
        boxSizing: 'border-box',
        height: '100%',
      }}>
        <div style={{
          display: 'none',
          flexDirection: 'column',
          border: '1px solid #ccc',
          padding: 10,
          borderRadius: 4,
          overflow: 'hidden'
        }}>
          <h6>命令列表</h6>
          <ul className='commands-container' style={{
            width: 180,
            overflow: 'auto',
            flex: 1
          }}>
            {
              commands.map((item: any) => (
                <DraggableItem key={item.id} id={item.id} cmd={item.cmd} containerId='commands' />
              ))
            }
          </ul>
        </div>
        <div style={{
          display: 'flex',
          flexDirection: 'column',
          flex: 1,
          border: '1px solid #ccc',
          padding: 10,
          borderRadius: 4,
          overflow: 'hidden',
          marginLeft: 4,
          marginRight: 4
        }}>
          <h6>执行流程</h6>
          <DroppableContainer id='allFlattenedItems' className='sortable-tree-container'>
            <SortableContext items={sortedIds} strategy={verticalListSortingStrategy}>
              {flattenedItems.map(({ parentId, depth, children, ...item }) => (
                <SortableTreeItem
                  key={item.id}
                  indentationWidth={indentationWidth}
                  indicator={indicator}
                  onCollapse={() => handleCollapse(item.id as string)}
                  onBreakPoint={() => handleAddBreakPoint(item.id as string)}
                  onRemove={removable ? () => handleRemove(item.id as string) : undefined}
                  childCount={getChildCount(items, item.id as string) + 1}
                  depth={item.id === activeId && projected ? projected.depth : depth}
                  breakpointCommand={breakpointIds.includes(item.id as string)} // 是否有断点
                  doneCommand={doneCommandIds.includes(item.id as string)} // 是否执行成功
                  warningCommand={warningCommandIds.includes(item.id as string)} // 是否执行有警告
                  errorCommand={errorCommandIds.includes(item.id as string)} // 是否执行失败
                  runningCommand={item.seq === nextCommandIndex} // 是否正在执行
                  commentCommand={item.cmd === 'comment'} // 是否注释命令
                  selectedCommand={item.id === editing.meta.selectedIndex}
                  {
                  ...item
                  }
                />
              ))}
            </SortableContext>
          </DroppableContainer>
        </div>

        {/* <RightSidebar /> */}

      </div>
      {createPortal(
        <DragOverlay
          dropAnimation={dropAnimationConfig}
          modifiers={indicator ? [adjustTranslate] : undefined}
        >
          {activeId && activeItem ? (
            <SortableTreeItem
              {...activeItem}
              children={null}
              id={activeId}
              depth={activeItem.depth}
              clone
              childCount={getChildCount(items, activeId) + 1}
              indentationWidth={indentationWidth}
            />
          ) : null}
        </DragOverlay>,
        document.body
      )}
    </DndContext>
  );
}


